package http

import (
	"github.com/edgexfoundry/edgex-go/internal/app/core/metadata/application"
	"github.com/edgexfoundry/edgex-go/internal/app/core/metadata/controller"
	requestDTO "github.com/edgexfoundry/edgex-go/internal/app/dtos/requests"
	responseDTO "github.com/edgexfoundry/edgex-go/internal/app/dtos/responses"
	"github.com/edgexfoundry/edgex-go/internal/app/models"
	aiotHttp "github.com/edgexfoundry/edgex-go/internal/app/pkg/controller/http"
	"github.com/edgexfoundry/edgex-go/internal/io"
	"github.com/edgexfoundry/edgex-go/internal/pkg"
	"github.com/edgexfoundry/edgex-go/internal/pkg/correlation"
	"github.com/edgexfoundry/edgex-go/internal/pkg/utils"
	"github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/container"
	"github.com/edgexfoundry/go-mod-bootstrap/v2/di"
	"github.com/edgexfoundry/go-mod-core-contracts/v2/common"
	commonDTO "github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/common"
	"github.com/edgexfoundry/go-mod-core-contracts/v2/errors"
	"math"
	"net/http"
)

type DeviceController struct {
	reader io.DtoReader
	dic    *di.Container
}

// NewDeviceController creates and initializes an DeviceController
func NewDeviceController(dic *di.Container) *DeviceController {
	return &DeviceController{
		reader: io.NewJsonDtoReader(),
		dic:    dic,
	}
}

func (dc *DeviceController) DevicesGet(w http.ResponseWriter, r *http.Request) {
	lc := container.LoggingClientFrom(dc.dic.Get)
	ctx := r.Context()
	// parse URL query string for offset, limit, and labels

	labels := utils.ParseQueryStringToStrings(r, common.Labels, common.CommaSeparator)

	page, err := utils.ParseQueryStringToInt(r, controller.Page, common.DefaultOffset, 0, math.MaxInt32)
	if err != nil {
		page = 0
	}
	pageSize, err := utils.ParseQueryStringToInt(r, controller.PageSize, common.DefaultLimit, 0, math.MaxInt32)
	if err != nil {
		pageSize = common.DefaultLimit
	}

	var devices []models.Device
	name := utils.ParseQueryStringToString(r, common.Name, "")
	productId := utils.ParseQueryStringToString(r, controller.ProductId, "")
	var totalCount uint32

	if name != "" {
		devices, totalCount, err = application.DevicesGetByName(name, productId, page*pageSize, pageSize, ctx, dc.dic)
		if err != nil {
			utils.WriteErrorResponse(w, ctx, lc, err, "")
			return
		}
	} else {
		devices, totalCount, err = application.DevicesGetByLabels(labels, productId, page*pageSize, pageSize, ctx, dc.dic)
		if err != nil {
			utils.WriteErrorResponse(w, ctx, lc, err, "")
			return
		}
	}

	response := responseDTO.NewDevicesResponse("", "", http.StatusOK, devices, page, pageSize, totalCount)
	utils.WriteHttpHeader(w, ctx, http.StatusOK)
	pkg.EncodeAndWriteResponse(response, w, lc)
}

func (dc *DeviceController) DeviceAdd(w http.ResponseWriter, r *http.Request) {
	if r.Body != nil {
		defer func() { _ = r.Body.Close() }()
	}

	lc := container.LoggingClientFrom(dc.dic.Get)
	ctx := r.Context()
	correlationId := correlation.FromContext(ctx)

	var reqDTOs requestDTO.DeviceAddRequest
	err := dc.reader.Read(r.Body, &reqDTOs)
	if err != nil {
		utils.WriteErrorResponse(w, ctx, lc, err, "")
		return
	}

	reqId := reqDTOs.RequestId
	id, err := application.DevicesAdd(reqDTOs.Device.ToDeviceModel(), ctx, dc.dic)

	var response interface{}
	if err != nil {
		lc.Error(err.Error(), common.CorrelationHeader, correlationId)
		lc.Debug(err.DebugMessages(), common.CorrelationHeader, correlationId)
		response = commonDTO.NewBaseResponse(
			reqId,
			err.Message(),
			err.Code())
	} else {
		response = responseDTO.NewDeviceAddResponse(reqId, "", http.StatusCreated, id)
	}

	utils.WriteHttpHeader(w, ctx, http.StatusMultiStatus)
	pkg.EncodeAndWriteResponse(response, w, lc)
}

func (dc *DeviceController) DeviceGetById(w http.ResponseWriter, r *http.Request) {
	lc := container.LoggingClientFrom(dc.dic.Get)
	ctx := r.Context()

	deviceId := utils.ParseQueryStringToString(r, common.Id, "")
	if deviceId == "" {
		utils.WriteErrorResponse(w, ctx, lc, errors.NewCommonEdgeX(errors.KindInvalidId, aiotHttp.ParamsError, nil), "")
		return
	}

	device, err := application.DeviceGetById(deviceId, ctx, dc.dic)
	if err != nil {
		utils.WriteErrorResponse(w, ctx, lc, err, "")
		return
	}

	response := responseDTO.NewDeviceResponse("", "", http.StatusOK, responseDTO.ToDeviceDTO(device))
	utils.WriteHttpHeader(w, ctx, http.StatusOK)
	pkg.EncodeAndWriteResponse(response, w, lc)
}

func (dc *DeviceController) DeviceUpdate(w http.ResponseWriter, r *http.Request) {
	lc := container.LoggingClientFrom(dc.dic.Get)
	ctx := r.Context()

	var reqDTOs requestDTO.DeviceUpdateRequest
	err := dc.reader.Read(r.Body, &reqDTOs)
	if err != nil {
		utils.WriteErrorResponse(w, ctx, lc, err, "")
		return
	}

	device := reqDTOs.Device.ToDeviceModel()
	err = application.DeviceUpdate(device, ctx, dc.dic)
	if err != nil {
		utils.WriteErrorResponse(w, ctx, lc, err, "")
		return
	}

	response := commonDTO.NewBaseResponse("", "", http.StatusOK)
	utils.WriteHttpHeader(w, ctx, http.StatusOK)
	pkg.EncodeAndWriteResponse(response, w, lc)
}

func (dc *DeviceController) DeviceDelete(w http.ResponseWriter, r *http.Request) {
	lc := container.LoggingClientFrom(dc.dic.Get)
	ctx := r.Context()

	// URL parameters
	ids := utils.ParseQueryStringToStrings(r, controller.Ids, ",")

	err := application.DeviceDelete(ids, ctx, dc.dic)
	if err != nil {
		utils.WriteErrorResponse(w, ctx, lc, err, "")
		return
	}

	response := commonDTO.NewBaseResponse("", "", http.StatusOK)
	utils.WriteHttpHeader(w, ctx, http.StatusOK)
	pkg.EncodeAndWriteResponse(response, w, lc)
}

func (dc *DeviceController) CountByDeviceStatus(w http.ResponseWriter, r *http.Request) {
	lc := container.LoggingClientFrom(dc.dic.Get)
	ctx := r.Context()

	device, err := application.CountByDeviceStatus(ctx, dc.dic)
	if err != nil {
		utils.WriteErrorResponse(w, ctx, lc, err, "")
		return
	}

	response := responseDTO.NewDeviceCountResponse("", "", http.StatusOK, responseDTO.ToDeviceCountDTO(device))
	utils.WriteHttpHeader(w, ctx, http.StatusOK)
	pkg.EncodeAndWriteResponse(response, w, lc)
}
